package core.xml;

import java.util.Collections;
import java.util.Map;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

import core.util.ArrayUtil;
import core.util.MapUtil;


public class XMLBuilder {

	private static final String XML_VERSION = "1.0";

	private StringBuilder xml = new StringBuilder();

	private boolean isEscapeXML = true;

	private boolean isEmptyIfNull = true;

	public XMLBuilder() {
	}

	public XMLBuilder(boolean isEscapeXML) {
		this.isEscapeXML = isEscapeXML;
	}

	public XMLBuilder(boolean isEscapeXML, boolean isEmptyIfNull) {
		this.isEscapeXML = isEscapeXML;
		this.isEmptyIfNull = isEmptyIfNull;
	}

	public XMLBuilder(Object obj) {
		append(obj);
	}

	public XMLBuilder(String name, Object value) {
		appendNode(name, value);
	}

	public XMLBuilder(String name, String[][] attributes, Object value) {
		appendNode(name, attributes, value);
	}

	public XMLBuilder(String name, Map<String, String> attributes, Object value) {
		appendNode(name, attributes, value);
	}

	public XMLBuilder append(Object obj) {
		if (obj instanceof XMLBuilder) {
			append((XMLBuilder) obj);

		} else if (obj instanceof Map) {
			append((Map<?, ?>) obj);

		} else {
			xml.append(escapeXML(convertToString(obj)));
		}

		return this;
	}

	public XMLBuilder append(Map<?, ?> map) {
		if (MapUtil.isNotEmpty(map)) {
			for (Map.Entry<?, ?> entry : map.entrySet()) {
				appendNode(ObjectUtils.toString(entry.getKey()), entry.getValue());
			}
		}

		return this;
	}

	public XMLBuilder append(XMLBuilder xmlBuilder) {
		if (xmlBuilder != null) {
			xml.append(xmlBuilder.xml);
		}

		return this;
	}

	public XMLBuilder appendCDATA(Object obj) {
		xml.append("<![CDATA[").append(convertToString(obj)).append("]]>");

		return this;
	}

	@SuppressWarnings("unchecked")
	public XMLBuilder appendNode(String nodeName, Object nodeValue) {
		return appendNode(nodeName, Collections.EMPTY_MAP, nodeValue);
	}

	public XMLBuilder appendNode(String nodeName, String[][] attributes, Object nodeValue) {
		return appendNode(nodeName, ArrayUtil.asMap(attributes), nodeValue);
	}

	public XMLBuilder appendNode(String nodeName, Map<String, String> attributes, Object nodeValue) {
		if (StringUtils.isNotEmpty(nodeName)) {
			appendNodeStartTag(nodeName, attributes).append(nodeValue).appendNodeEndTag(nodeName);
		}

		return this;
	}

	@SuppressWarnings("unchecked")
	public XMLBuilder appendNotEmptyNode(String nodeName, Object nodeValue) {
		return appendNotEmptyNode(nodeName, Collections.EMPTY_MAP, nodeValue);
	}

	public XMLBuilder appendNotEmptyNode(String nodeName, String[][] attributes, Object nodeValue) {
		return appendNotEmptyNode(nodeName, ArrayUtil.asMap(attributes), nodeValue);
	}

	public XMLBuilder appendNotEmptyNode(String nodeName, Map<String, String> attributes, Object nodeValue) {
		if (nodeValue != null) {
			XMLBuilder value = new XMLBuilder(isEscapeXML, isEmptyIfNull).append(nodeValue);
			if (!value.isEmpty()) {
				appendNode(nodeName, attributes, value);
			}
		}

		return this;
	}

	public XMLBuilder appendCDATANode(String nodeName, Object nodeValue) {
		return appendCDATANode(nodeName, Collections.emptyMap(), nodeValue);
	}

	public XMLBuilder appendCDATANode(String nodeName, Object[][] attributes, Object nodeValue) {
		return appendCDATANode(nodeName, ArrayUtil.asMap(attributes), nodeValue);
	}

	public XMLBuilder appendCDATANode(String nodeName, Map<?, ?> attributes, Object nodeValue) {
		if (StringUtils.isNotEmpty(nodeName)) {
			appendNodeStartTag(nodeName, attributes).appendCDATA(nodeValue).appendNodeEndTag(nodeName);
		}

		return this;
	}

	public XMLBuilder appendNotEmptyCDATANode(String nodeName, Object nodeValue) {
		return appendNotEmptyCDATANode(nodeName, Collections.emptyMap(), nodeValue);
	}

	public XMLBuilder appendNotEmptyCDATANode(String nodeName, Object[][] attributes, Object nodeValue) {
		return appendNotEmptyCDATANode(nodeName, ArrayUtil.asMap(attributes), nodeValue);
	}

	public XMLBuilder appendNotEmptyCDATANode(String nodeName, Map<?, ?> attributes, Object nodeValue) {
		if (nodeValue != null) {
			String value = convertToString(nodeValue);
			if (StringUtils.isNotEmpty(value)) {
				appendCDATANode(nodeName, attributes, value);
			}
		}

		return this;
	}

	public XMLBuilder appendNodeStartTag(String nodeName) {
		return appendNodeStartTag(nodeName, Collections.emptyMap());
	}

	public XMLBuilder appendNodeStartTag(String nodeName, Object[][] attrs) {
		return appendNodeStartTag(nodeName, ArrayUtil.asMap(attrs));
	}

	public XMLBuilder appendNodeStartTag(String nodeName, Map<?, ?> attrs) {
		if (StringUtils.isNotEmpty(nodeName)) {
			xml.append("<").append(nodeName);
			if (MapUtil.isNotEmpty(attrs)) {
				for (Map.Entry<?, ?> attr : attrs.entrySet()) {
					appendNodeAttribute(ObjectUtils.toString(attr.getKey()), attr.getValue());
				}
			}
			xml.append(">");
		}

		return this;
	}

	public XMLBuilder appendNodeEndTag(String nodeName) {
		if (StringUtils.isNotEmpty(nodeName)) {
			xml.append("</").append(nodeName).append(">");
		}

		return this;
	}

	public XMLBuilder appendHeader(String encoding) {
		return appendHeader(XML_VERSION, encoding);
	}

	public XMLBuilder appendHeader(String version, String encoding) {
		if (StringUtils.isNotEmpty(encoding)) {
			xml.append("<?xml version=\"").append(version).append("\" encoding=\"").append(encoding).append("\"?>");
		}

		return this;
	}

	public int length() {
		return xml.length();
	}

	public boolean isEmpty() {
		return (xml.length() == 0);
	}

	public XMLBuilder clear() {
		xml.delete(0, xml.length());
		return this;
	}

	public XMLBuilder setEscapeXML(boolean isEscapeXML) {
		this.isEscapeXML = isEscapeXML;
		return this;
	}

	public boolean isEscapeXML() {
		return isEscapeXML;
	}

	public XMLBuilder setEmptyIfNull(boolean isEmptyIfNull) {
		this.isEmptyIfNull = isEmptyIfNull;

		return this;
	}
	
	public boolean isEmptyIfNull() {
		return isEmptyIfNull;
	}
	
	@Override
	public String toString() {
		return xml.toString();
	}

	private XMLBuilder appendNodeAttribute(String name, Object value) {
		if (StringUtils.isNotEmpty(name)) {
			xml.append(" ").append(name).append("=\"").append(escapeXML(convertToString(value))).append("\"");
		}

		return this;
	}

	private String convertToString(Object obj) {
		return (isEmptyIfNull ? ObjectUtils.toString(obj) : String.valueOf(obj));
	}

	/**
	 *
	 * isEscapeXML = true -> escapeXML("&lt;host port=&quot;8080&quot; /&gt;") =
	 * &amp;lt;host port=&amp;quot;8080&amp;quot; /&amp;gt; <br />
	 * isEscapeXML = false -> escapeXML("&lt;host port=&quot;8080&quot; /&gt;")
	 * = &lt;host port=&quot;8080&quot; /&gt;
	 * 
	 * @param xml
	 */
	private String escapeXML(String xml) {
		return (isEscapeXML ? StringEscapeUtils.escapeXml(xml) : xml);
	}
}
